package util;

import util.exceptions.UnknownElementException;

import java.util.ArrayList;

public class Molecule {
    private ArrayList<MoleculeComponent> parts;
    private String name;
    private int charge;

    /**
     * Creates a new Molecule
     *
     * @param c The formula which represents the molecule
     * @throws UnknownElementException if molecule does not exist
     */
    public Molecule(String c) throws UnknownElementException {
        name = c;
        c = removeCharge(c);
        c = removeParentheses(c);

        ArrayList<String> elements = new ArrayList<String>();
        ArrayList<Integer> multiplier = new ArrayList<Integer>();
        int startIndex;
        int endIndex = 0;
        while (endIndex < c.length()) {
            startIndex = endIndex;
            endIndex = findEndName(c, startIndex);
            if (endIndex != c.length())
                elements.add(c.substring(startIndex, endIndex));
            else {
                elements.add(c.substring(startIndex, endIndex));
                multiplier.add(1);
                break;
            }
            startIndex = endIndex;
            endIndex = findEndNum(c, startIndex);
            if (startIndex == endIndex)
                multiplier.add(1);
            else
                multiplier.add(Integer.parseInt(c.substring(startIndex, endIndex)));
        }
        parts = new ArrayList<MoleculeComponent>();
        for (int m = 0; m < elements.size(); m++) {
            parts.add(new MoleculeComponent(elements.get(m), multiplier.get(m)));
        }
        checkForMultiples();
    }
    /**
     * Checks for whether there are multiples of the same Atom. For example, COOH would have multiples of O
     * @throws util.exceptions.UnknownElementException s*/
    private void checkForMultiples() throws UnknownElementException {
        ArrayList<Atom> atoms = getAtoms();
        for (Atom m : atoms) {
            int count = countOccurences(m);
            if (count > 1) {
                int totalMultiplicity = 0;
                for (int i = 0; i < parts.size(); i++) {
                    if (parts.get(i).getElement().equals(m)) {
                        totalMultiplicity += parts.get(i).getMultiplicity();
                        parts.remove(i);
                        i--;
                    }
                }
                parts.add(new MoleculeComponent(m.getSymbol(), totalMultiplicity));
            }
        }
    }

    private int countOccurences(Atom m) {
        int count = 0;
        for (MoleculeComponent c : parts) {
            if (c.getElement().equals(m)) {
                count++;
            }
        }
        return count;
    }

    public static String removeParentheses(String c) {
        while (c.indexOf("(") != -1) {
            String endString = "";
            int leftLocation = c.indexOf("(");
            int rightLocation = c.indexOf(")");
            String subMolecule = c.substring(leftLocation + 1, rightLocation);
            int startIndex;
            int endIndex = 0;
            String k = c.substring(rightLocation + 1, findEndNum(c, rightLocation + 1));
            int coeff;
            if (k.length() != 0)
                coeff = Integer.parseInt(k);
            else
                coeff = 1;
            c = c.substring(0, rightLocation + 1) + c.substring(findEndNum(c, rightLocation + 1));


            while (endIndex < subMolecule.length()) {
                startIndex = endIndex;
                endIndex = findEndName(subMolecule, startIndex);
                String temp;
                if (endIndex != subMolecule.length()) {
                    temp = subMolecule.substring(startIndex, endIndex);
                } else {
                    temp = subMolecule.substring(startIndex, endIndex);
                    endString = endString + temp + coeff;
                    break;
                }
                startIndex = endIndex;
                endIndex = findEndNum(subMolecule, startIndex);
                if (startIndex == endIndex) {
                    endString = endString + temp + coeff;
                } else {
                    endString = endString + temp + coeff * Integer.parseInt(subMolecule.substring(startIndex, endIndex));
                }
            }

            c = c.substring(0, leftLocation) + endString + c.substring(rightLocation + 1);

        }
        return c;
    }

    public String removeCharge(String c) {
        if (c.contains("{")) {
            int left = c.indexOf("{");
            int right = c.indexOf("}");
            String charge = "";
            if (left != -1) {
                charge = c.substring(left + 1, right);
                int multiplier = 1;
                if (charge.contains("+")) {
                    charge = charge.replace("+", "");
                } else if (charge.contains("-")) {
                    charge = charge.replace("-", "");
                    multiplier = -1;
                }
                if (charge.length() != 0)
                    this.charge = Integer.parseInt(charge.replaceAll(" ", "")) * multiplier;
                else {
                    this.charge = multiplier;
                }
                return c.replace(c.substring(left), "");
            }
        }
        return c;
    }


    /**
     * Finds the index of the end of the element symbol
     *
     * @param c     The string which represents the Molecule
     * @param start The starting index
     * @return The end index
     */
    private static int findEndName(String c, int start) {
        start++;
        for (; start < c.length(); start++) {
            if ((c.charAt(start) <= 57 && c.charAt(start) >= 48) || (c.charAt(start) <= 91 && c.charAt(start) >= 64)) {
                return start;
            }
        }
        return start;
    }

    /**
     * Finds the index of the end of the element coefficient
     *
     * @param c     The string which represents the Molecule
     * @param start The starting index
     * @return The end index
     */
    private static int findEndNum(String c, int start) {
        for (; start < c.length(); start++) {
            if (!(c.charAt(start) <= 57 && c.charAt(start) >= 48)) {
                return start;
            }
        }
        return start;
    }

    /**
     * Gets the elements within the Molecule
     *
     * @return An array of MoleculeComponents
     */
    public ArrayList<MoleculeComponent> getElements() {
        return parts;
    }


    /**
     * Whether or not the atom contains the specified component
     *
     * @param a The component to be found
     * @return true if it is contained, false otherwise
     */
    public boolean contains(MoleculeComponent a) {
        for (MoleculeComponent j : parts) {
            if (j.equals(a))
                return true;
        }
        return false;
    }

    public boolean contains(Atom a) {
        for (MoleculeComponent j : parts) {
            if (j.getElement().equals(a))
                return true;
        }
        return false;
    }

    /**
     * Gets the amount of atoms specified
     *
     * @param a The atom specified
     * @return Returns the multiplicity if it is contained, 0 otherwise
     */
    public int getCount(Atom a) {
        for (MoleculeComponent j : parts) {
            if (j.getElement().equals(a))
                return j.getMultiplicity();
        }
        return 0;
    }

    /**
     * Gets the atoms which are contained in the equation
     *
     * @return An ArrayList of the atoms
     */
    private ArrayList<Atom> getAtoms() {
        ArrayList<Atom> atoms = new ArrayList<Atom>();
        for (MoleculeComponent g : parts) {
            if (!atoms.contains(g.getElement())) {
                atoms.add(g.getElement());
            }
        }
        return atoms;
    }

    public boolean isSingleElement() {
        if (parts.size() == 1)
            return true;
        return false;
    }

    @Override
    public boolean equals(Object o) {
        Molecule other;
        if (o instanceof Molecule)
            other = (Molecule) o;
        else
            return false;
        if (other.getElements().size() != parts.size() || charge != other.getCharge())
            return false;

        for (MoleculeComponent m : other.getElements()) {
            if (!contains(m)) {
                return false;
            }
        }
        return true;
    }

    @Override
    public String toString() {
        return name;
    }

    public int getCharge() {
        return charge;
    }



}
